namespace designpatternsplusplus.decorator.Support
{
    #region

    using System;
    using System.Diagnostics;

    #endregion

    /// <summary>
    /// Helpers around the issues of rounding in large monetary
    /// floating point ops
    /// Likely refactoring would move more quantatative 
    /// responsibilities here (arguably) to make this
    /// a kind of 'quant engine' for more abstraction
    /// and ability to invoke more 'domain specific' names
    /// that this could map to the math
    /// </summary>
    public sealed class FloatingPointService
    {
        const double roundingbase = .05;

        readonly double _baseAmount;

        /// <summary>
        /// Initializes a new instance of the
        ///  <see cref="FloatingPointService"/> class.
        /// </summary>
        /// <param name="NetAmount">The net amount.</param>
        FloatingPointService(double NetAmount)
        {
            _baseAmount = NetAmount;
        }

        /// <summary>
        /// Applies the tax using an 10 = 10% representation
        /// </summary>
        /// <param name="taxRate">The tax rate.</param>
        /// <returns></returns>
        public double ApplyTax(double taxRate)
        {
            return _baseAmount*taxRate;
        }

        /// <summary>
        /// The rounding rules for sales tax are that for a tax rate of n%, a shelf price of p contains
        /// (np/100 rounded up to the nearest 0.05) amount of sales tax.
        /// </summary>
        /// <param name="CalculateTax">if set to <c>true</c> [calculate tax].</param>
        /// <param name="taxRate">The tax rate.</param>
        /// <param name="CurrentAmount">The current amount.</param>
        /// <returns></returns>
        public static double RoundTaxAmount
            (bool CalculateTax, double taxRate, double CurrentAmount)
        {
            var _dec = new FloatingPointService(CurrentAmount);
            var tax = CalculateTax ? _dec.ApplyTax(taxRate) : 0;

            if (tax > 0)
            {
                Trace.WriteLine
                    (String.Format
                         ("OnBeforeTaxRound: Current= {0} TaxRate= {1}",
                          tax.ToString("C"),
                          taxRate));

                tax = AdjustTaxRoundingRule(tax);

                Trace.WriteLine
                    (String.Format
                         ("Post Rounding: Tax calculated: Result ={0} ", tax.ToString("C")));
            }
            else
            {
                Trace.WriteLine
                    ("Skipped: Failed inclusion rule. Value was " +
                     CurrentAmount.ToString("C"));
            }

            return tax;
        }

        /// <summary>
        /// Adjusts the tax rounding rule as
        /// specific to the upper .05 if not divisible by
        /// it with no remainder
        /// </summary>
        /// <param name="tax">The tax.</param>
        /// <returns></returns>
        static double AdjustTaxRoundingRule(double tax)
        {
            var _ieeeRemainder = DetermineRemainder(ref tax);

            if (_ieeeRemainder != 0)
            {
                Trace.WriteLine
                    ("Remainder! " + _ieeeRemainder.ToString("C") +
                     " I'll be back. Must call myself. Don't ask.");
                tax += Math.Abs(_ieeeRemainder);
                return AdjustTaxRoundingRule(tax);
            }

            return tax;
        }

        /// <summary>
        /// Determines the remainder.
        /// </summary>
        /// <param name="tax">The tax.</param>
        /// <returns></returns>
        static double DetermineRemainder(ref double tax)
        {
            tax = Math.Round(tax, 2);
            return Math.Round(Math.IEEERemainder(tax, roundingbase), 2);
        }
    }
}